package com.arashivision.webrtc;

import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;
import android.view.Surface;

import org.webrtc.Logging;

import java.util.List;

/**
 * Created by vans on 29/12/17.
 */

public class OneRTC
{
    private static final String TAG = "OneRTC";
    private OneRTCImplement mImpl;
    private HandlerThread mHandlerThread;
    private Handler mHandler;
    private boolean mReleased;
    private Looper mLooper;
    private boolean mRecordStarted;

    // execute task on Looper which have no return value
    private void syncExecute(final Runnable runnable) {
        if(mReleased)
            throw new RuntimeException("Camera released");
        final Throwable[] throwables = new Throwable[1];
        final TaskWaiter taskWaiter = new TaskWaiter();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    runnable.run();
                } catch(Throwable t) {
                    throwables[0] = t;
                }
                taskWaiter.done();
            }
        });
        taskWaiter.await();
        if(throwables[0] != null) {
            Log.e(TAG, "[CameraWrapper] OneRTCImplement execute error: " + throwables[0]);
            throwables[0].printStackTrace();
            throw new RuntimeException(throwables[0]);
        }
    }


    // execute task on Looper which have return value
    private interface Task<T> {
        T run();
    }

    private <T> T syncExecuteTask(final Task task) {
        if(mReleased)
            throw new RuntimeException("Camera released");
        final Object[] result = new Object[2];
        final TaskWaiter taskWaiter = new TaskWaiter();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    result[1] = task.run();
                } catch(Throwable t) {
                    result[0] = t;
                }
                taskWaiter.done();
            }
        });
        taskWaiter.await();
        if(result[0] != null) {
            Log.e(TAG, "[CameraWrapper] OneRTCImplement execute error: " + result[0]);
            ((Throwable)result[0]).printStackTrace();
            throw new RuntimeException((Throwable)result[0]);
        }
        return (T)result[1];
    }

    /**
     * Construct OneRTC instance.
     * @param context could be application context
     * @param callbacks camera callback
     * @param callbackHandler callback will post this handler to execute
     */
    public OneRTC(final Context context,
                     final OneRTCCallbacks callbacks, final Handler callbackHandler) {
        mHandlerThread = new HandlerThread("OneRTC");
        mHandlerThread.start();
        mLooper = mHandlerThread.getLooper();
        mHandler = new Handler(mHandlerThread.getLooper());
        syncExecute(new Runnable() {
            @Override
            public void run() {
                mImpl = new OneRTCImplement(mLooper, context, callbacks, callbackHandler);
            }
        });
    }

    public synchronized void setInfoUpdateListener(final RTCInfoListener l,final Handler handler) {
        syncExecute(new Runnable() {
            @Override
            public void run() {
                mImpl.setInfoUpdateListener(handler, l);
            }
        });
    }

    public synchronized void setSurface(final Surface mFullSurface,final Surface mPipSurface) {
        syncExecute(new Runnable() {
            @Override
            public void run() {
                mImpl.setSurface(mFullSurface, mPipSurface);
            }
        });
    }

    //switch pip/full screen
    public void switchFeeds() {
        syncExecute(new Runnable() {
            @Override
            public void run() {
                mImpl.switchFeeds();
            }
        });
    }

    //switch back/front camera in mobile
    public void switchCamera() {
        syncExecute(new Runnable() {
            @Override
            public void run() {
                mImpl.switchCamera();
            }
        });
    }

    /**
     * Open camera, it will:
     * <p> 1. Find the supported camera which has attached to mobile
     * <p> 2. Check whether app has permission to access the camera device
     * <p> 3. If app current doesn't have permission to access the camera device, it will request
     * permission with a dialog.
     * <p> 4. When app has granted permission to access the camera device, it will start initialize
     * it with camera protocol, this not besides setup video streaming parameters(see {@link #startStreaming()})
     *
     * <p> This method will return immediately without waiting the result. When success
     * {@link Callbacks#onOpenComplete()} will post to  CallbackHandler to execute; Otherwise,
     * {@link Callbacks#onError(int, int, String) will post to CallbackHandler to excute}
     *
     * <p>Some issues cause open failed: </p>
     * <p>1. No supported camera attached. </p>
     * <p>2. Permission to access the camera device is not granted by user</p>
     * <p>3. Other app is using this camera device</p>
     * <p>4. Initialize camera failed: may cause by usb detached; usb connection not stable; camera
     * bug; compatibility reasons</p>
     *
     * @param panoOffset panoramic stitching parameter
     */
    public synchronized void connect(final RTCConnectInfo mConnectInfo) {
        syncExecute(new Runnable() {
            @Override
            public void run() {
                mImpl.connect(mConnectInfo);
            }
        });
    }

    public synchronized void disconnect() {
        syncExecute(new Runnable() {
            @Override
            public void run() {
                mImpl.disconnect();
            }
        });
    }

    public synchronized void resume() {
        syncExecute(new Runnable() {
            @Override
            public void run() {
                mImpl.resume();
            }
        });
    }

    public synchronized void stop() {
        syncExecute(new Runnable() {
            @Override
            public void run() {
                mImpl.stop();
            }
        });
    }

    private static String stackTraceToString(StackTraceElement[] stackTraceElements) {
        StringBuilder sb = new StringBuilder();
        for (StackTraceElement element : stackTraceElements) {
            sb.append(element.toString());
            sb.append("\n");
        }
        return sb.toString();
    }

    /**
     * Release OneRTC instance.
     * <p>All resource will be released. If not closed, it will close firstly.</p>
     * <p>After released, you can't use the instance</p>
     */
    public synchronized void release()
    {
        Log.i(TAG, "release OneRTC: \n" + stackTraceToString(Thread.currentThread().getStackTrace()));
        if(mReleased)
            return;

        syncExecute(new Runnable() {
            @Override
            public void run() {
                mImpl.release();
            }
        });
        mHandlerThread.quit();
        try {
            mHandlerThread.join();
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        mHandlerThread = null;
        mReleased = true;
    }

    @Override
    protected void finalize() throws Throwable {
        if(!mReleased)
            release();
        super.finalize();
    }

}
